package com.library.ble.conn;

import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.os.Handler;
import android.text.TextUtils;

import com.library.ble.BleDeviceManager;
import com.library.ble.exception.GattException;
import com.library.ble.exception.InitiatedException;
import com.library.ble.exception.OtherException;
import com.library.ble.utils.HexUtil;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;


public class BleDataConnector {
    public static String HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb";
    public static String CLIENT_CHARACTERISTIC_CONFIG = "0000-1000-8000-00805f9b34fb";
    public static final UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(HEART_RATE_MEASUREMENT);
    private static final int MSG_NOTIY_CHA = 6;
    private static final int MSG_NOTIY_DES = 7;
    private static final int MSG_READ_CHA = 3;
    private static final int MSG_READ_DES = 4;
    private static final int MSG_READ_RSSI = 5;
    private static final int MSG_WRIATE_CHA = 1;
    private static final int MSG_WRIATE_DES = 2;
    private static final String TAG = BleDataConnector.class.getSimpleName();
    private BluetoothGatt bluetoothGatt;
    private BluetoothGattCharacteristic characteristic;
    private BluetoothGattDescriptor descriptor;
    private Handler handler;
    private BleDeviceManager bleDeviceManager;
    private BluetoothGattService service;
    private int timeOutMillis;

    public void closeHandler() {
        if(this.handler!=null){
            this.handler.removeCallbacksAndMessages(null);
            this.handler=null;
        }
    }

    public BleDataConnector(BleDeviceManager bleDeviceManager) {
        this.bleDeviceManager = bleDeviceManager;
        this.timeOutMillis = 10*1000;
        this.handler = bleDeviceManager.getMainHandler();
        this.bluetoothGatt = bleDeviceManager.getBluetoothGatt();
    }


    public BleDataConnector withUUID(UUID serviceUUID, UUID charactUUID, UUID descriptorUUID) {
        if (!(serviceUUID == null || this.bluetoothGatt == null)) {
            this.service = this.bluetoothGatt.getService(serviceUUID);
        }
        if (!(this.service == null || charactUUID == null)) {
            this.characteristic = this.service.getCharacteristic(charactUUID);
        }
        if (!(this.characteristic == null || descriptorUUID == null)) {
            this.descriptor = this.characteristic.getDescriptor(descriptorUUID);
        }
        return this;
    }
    public BleDataConnector withUUIDForHead(String serviceUUID, String charactUUID, String descriptorUUID) {

        if(this.bluetoothGatt != null&& !TextUtils.isEmpty(serviceUUID)){
            this.service = this.bluetoothGatt.getService(formUUID(returnUUID(serviceUUID)));
        }

        if (this.service != null &&!TextUtils.isEmpty(charactUUID)) {
            this.characteristic = this.service.getCharacteristic(formUUID(returnUUID(charactUUID)));
        }
        if (this.characteristic != null &&!TextUtils.isEmpty(descriptorUUID)) {
            this.descriptor = this.characteristic.getDescriptor(formUUID(returnUUID(descriptorUUID)));
        }
        return this;
    }
    
    public BleDataConnector withUUIDStringHead(String serviceUUID, String charactUUID, String descriptorUUID) {
        return withUUID(formUUID(serviceUUID), formUUID(charactUUID), formUUID(descriptorUUID));
    }

    public BleDataConnector withUUIDString(String serviceUUID, String charactUUID, String descriptorUUID) {
        return withUUID(formUUID(serviceUUID), formUUID(charactUUID), formUUID(descriptorUUID));
    }

    private UUID formUUID(String uuid) {
        return uuid == null ? null : UUID.fromString(uuid);
    }

    private String returnUUID(String head){
        if(TextUtils.isEmpty(head)) return null;
        StringBuffer uuid=new StringBuffer();
        uuid.append(head);
        uuid.append(CLIENT_CHARACTERISTIC_CONFIG);
        return uuid.toString().trim();
    }
    public boolean writeCharacteristic(byte[] data, BleCharactCallback bleCallback) {
        return writeCharacteristic(getCharacteristic(), data, bleCallback);
    }

    public boolean writeCharacteristic(BluetoothGattCharacteristic charact, byte[] data, BleCharactCallback bleCallback) {
        //LogUtil.d(TAG, charact.getUuid() + " characteristic write bytes: " + Arrays.toString(data) + " ,hex: " + HexUtil.encodeHexStr(data));
        handleCharacteristicWriteCallback(bleCallback);
        charact.setValue(data);
        return handleAfterInitialed(getBluetoothGatt().writeCharacteristic(charact), bleCallback);
    }

    public boolean writeDescriptor(byte[] data, BleDescriptorCallback bleCallback) {
        return writeDescriptor(getDescriptor(), data, bleCallback);
    }

    public boolean writeDescriptor(BluetoothGattDescriptor descriptor, byte[] data, BleDescriptorCallback bleCallback) {
       // LogUtil.d(TAG, descriptor.getUuid() + " descriptor write bytes: " + Arrays.toString(data) + " ,hex: " + HexUtil.encodeHexStr(data));
        handleDescriptorWriteCallback(bleCallback);
        descriptor.setValue(data);
        return handleAfterInitialed(getBluetoothGatt().writeDescriptor(descriptor), bleCallback);
    }

    public boolean readCharacteristic(BleCharactCallback bleCallback) {
        return readCharacteristic(getCharacteristic(), bleCallback);
    }

    public boolean readCharacteristic(BluetoothGattCharacteristic charact, BleCharactCallback bleCallback) {
        if ((this.characteristic.getProperties() | MSG_WRIATE_DES) > 0) {
            setCharacteristicNotification(getBluetoothGatt(), charact, false);
            handleCharacteristicReadCallback(bleCallback);
            return handleAfterInitialed(getBluetoothGatt().readCharacteristic(charact), bleCallback);
        } else if (bleCallback == null) {
            return false;
        } else {
            bleCallback.onFailure(new OtherException("Characteristic [is not] readable!"));
            return false;
        }
    }

    public boolean readDescriptor(BleDescriptorCallback bleCallback) {
        return readDescriptor(getDescriptor(), bleCallback);
    }

    public boolean readDescriptor(BluetoothGattDescriptor descriptor, BleDescriptorCallback bleCallback) {
        handleDescriptorReadCallback(bleCallback);
        return handleAfterInitialed(getBluetoothGatt().readDescriptor(descriptor), bleCallback);
    }

    public boolean readRemoteRssi(BleRssiCallback bleCallback) {
        handleRSSIReadCallback(bleCallback);
        return handleAfterInitialed(getBluetoothGatt().readRemoteRssi(), bleCallback);
    }

    public boolean enableCharacteristicNotification(BleCharactCallback bleCallback) {
        return enableCharacteristicNotification(getCharacteristic(), bleCallback);
    }

    public boolean enableCharacteristicNotification(BluetoothGattCharacteristic charact, BleCharactCallback bleCallback) {
        if ((charact.getProperties() | 16) > 0) {
            handleCharacteristicNotificationCallback(bleCallback);
            return setCharacteristicNotification(getBluetoothGatt(), charact, true);
        }
        if (bleCallback != null) {
            bleCallback.onFailure(new OtherException("Characteristic [not supports] readable!"));
        }
        return false;
    }

    public boolean enableDescriptorNotification(BleDescriptorCallback bleCallback) {
        return enableDescriptorNotification(getDescriptor(), bleCallback);
    }

    public boolean enableDescriptorNotification(BluetoothGattDescriptor descriptor, BleDescriptorCallback bleCallback) {
        handleDescriptorNotificationCallback(bleCallback);
        return setDescriptorNotification(getBluetoothGatt(), descriptor, true);
    }

    private boolean handleAfterInitialed(boolean initiated, BleCallback bleCallback) {
        if (bleCallback != null) {
            if (initiated) {
                bleCallback.onInitiatedSuccess();
            } else {
                bleCallback.onFailure(new InitiatedException());
            }
        }
        return initiated;
    }

    public boolean setNotification(boolean enable) {
        return setNotification(getBluetoothGatt(), getCharacteristic(), getDescriptor(), enable);
    }

    public boolean setNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, boolean enable) {
        return setCharacteristicNotification(gatt, characteristic, enable) && setDescriptorNotification(gatt, descriptor, enable);
    }

    public boolean setCharacteristicNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, boolean enable) {
        if (gatt == null || characteristic == null) {
            return false;
        }
//        LogUtil.d(TAG, "Characteristic set notification value: " + enable);
        boolean success = gatt.setCharacteristicNotification(characteristic, enable);
        if (!UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
            return success;
        }
//        LogUtil.d(TAG, "Heart Rate Measurement set [descriptor] notification value: " + enable);
        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        gatt.writeDescriptor(descriptor);
        return success;
    }

    public boolean setDescriptorNotification(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, boolean enable) {
        if (gatt == null || descriptor == null) {
            return false;
        }
//        LogUtil.d(TAG, "Descriptor set notification value: " + enable);
        if (enable) {
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        } else {
            descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
        }
        return gatt.writeDescriptor(descriptor);
    }

    private void handleCharacteristicWriteCallback(final BleCharactCallback bleCallback) {
        if (bleCallback != null) {
            listenAndTimer(bleCallback, MSG_WRIATE_CHA, new BluetoothGattCallback() {
                public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                    BleDataConnector.this.handler.removeMessages(BleDataConnector.MSG_WRIATE_CHA, this);
                    BleDataConnector.this.bleDeviceManager.removeGattCallback(bleCallback.getBluetoothGattCallback());
                    if (status == 0) {
                        bleCallback.onSuccess(characteristic);
                    } else {
                        bleCallback.onFailure(new GattException(status));
                    }
                }
            });
        }
    }

    private void handleDescriptorWriteCallback(final BleDescriptorCallback bleCallback) {
        if (bleCallback != null) {
            listenAndTimer(bleCallback, MSG_WRIATE_DES, new BluetoothGattCallback() {
                public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
                    BleDataConnector.this.handler.removeMessages(BleDataConnector.MSG_WRIATE_DES, this);
                    BleDataConnector.this.bleDeviceManager.removeGattCallback(bleCallback.getBluetoothGattCallback());
                    if (status == 0) {
                        bleCallback.onSuccess(descriptor);
                    } else {
                        bleCallback.onFailure(new GattException(status));
                    }
                }
            });
        }
    }

    private void handleCharacteristicReadCallback(final BleCharactCallback bleCallback) {
        if (bleCallback != null) {
            listenAndTimer(bleCallback, MSG_READ_CHA, new BluetoothGattCallback() {
                AtomicBoolean msgRemoved = new AtomicBoolean(false);

                public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                    if (!this.msgRemoved.getAndSet(true)) {
                        BleDataConnector.this.handler.removeMessages(BleDataConnector.MSG_READ_CHA, this);
                    }
                    BleDataConnector.this.bleDeviceManager.removeGattCallback(bleCallback.getBluetoothGattCallback());
                    if (status == 0) {
                        bleCallback.onSuccess(characteristic);
                    } else {
                        bleCallback.onFailure(new GattException(status));
                    }
                }
            });
        }
    }

    private void handleDescriptorReadCallback(final BleDescriptorCallback bleCallback) {
        if (bleCallback != null) {
            listenAndTimer(bleCallback, MSG_READ_DES, new BluetoothGattCallback() {
                AtomicBoolean msgRemoved = new AtomicBoolean(false);

                public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
                    if (!this.msgRemoved.getAndSet(true)) {
                        BleDataConnector.this.handler.removeMessages(BleDataConnector.MSG_READ_DES, this);
                    }
                    if (status == 0) {
                        bleCallback.onSuccess(descriptor);
                    } else {
                        bleCallback.onFailure(new GattException(status));
                    }
                }
            });
        }
    }

    private void handleRSSIReadCallback(final BleRssiCallback bleCallback) {
        if (bleCallback != null) {
            listenAndTimer(bleCallback, MSG_READ_RSSI, new BluetoothGattCallback() {
                public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
                    BleDataConnector.this.handler.removeMessages(BleDataConnector.MSG_READ_RSSI, this);
                    if (status == 0) {
                        bleCallback.onSuccess(rssi);
                    } else {
                        bleCallback.onFailure(new GattException(status));
                    }
                }
            });
        }
    }

    private void handleCharacteristicNotificationCallback(final BleCharactCallback bleCallback) {
        if (bleCallback != null) {
            listenAndTimer(bleCallback, MSG_NOTIY_CHA, new BluetoothGattCallback() {
                AtomicBoolean msgRemoved = new AtomicBoolean(false);

                public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
                    if (!this.msgRemoved.getAndSet(true)) {
                        BleDataConnector.this.handler.removeMessages(BleDataConnector.MSG_NOTIY_CHA, this);
                    }
                    bleCallback.onSuccess(characteristic);
                }
            });
        }
    }

    private void handleDescriptorNotificationCallback(final BleDescriptorCallback bleCallback) {
        if (bleCallback != null) {
            listenAndTimer(bleCallback, MSG_NOTIY_DES, new BluetoothGattCallback() {
                public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
                    BleDataConnector.this.handler.removeMessages(BleDataConnector.MSG_NOTIY_DES, this);
                    if (status == 0) {
                        bleCallback.onSuccess(descriptor);
                    } else {
                        bleCallback.onFailure(new GattException(status));
                    }
                }
            });
        }
    }

    private void listenAndTimer(BleCallback bleCallback, int what, BluetoothGattCallback callback) {
        bleCallback.setBluetoothGattCallback(callback);
        this.bleDeviceManager.addGattCallback(callback);
        this.handler.sendMessageDelayed(this.handler.obtainMessage(what, bleCallback), (long) this.timeOutMillis);
    }

    public BluetoothGatt getBluetoothGatt() {
        return this.bluetoothGatt;
    }

    public BleDataConnector setBluetoothGatt(BluetoothGatt bluetoothGatt) {
        this.bluetoothGatt = bluetoothGatt;
        return this;
    }

    public BluetoothGattService getService() {
        return this.service;
    }

    public BleDataConnector setService(BluetoothGattService service) {
        this.service = service;
        return this;
    }

    public BluetoothGattCharacteristic getCharacteristic() {
        return this.characteristic;
    }

    public BleDataConnector setCharacteristic(BluetoothGattCharacteristic characteristic) {
        this.characteristic = characteristic;
        return this;
    }

    public BluetoothGattDescriptor getDescriptor() {
        return this.descriptor;
    }

    public BleDataConnector setDescriptor(BluetoothGattDescriptor descriptor) {
        this.descriptor = descriptor;
        return this;
    }

    public int getTimeOutMillis() {
        return this.timeOutMillis;
    }

    public BleDataConnector setTimeOutMillis(int timeOutMillis) {
        this.timeOutMillis = timeOutMillis;
        return this;
    }






}
